"""
Extension Priority System

Advanced priority and conflict resolution system for managing multiple extensions
that can handle the same configuration files or provide similar functionality.
"""

import logging
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from enum import Enum, auto
from typing import Dict, List, Optional, Set, Tuple, Any, Callable
from pathlib import Path

logger = logging.getLogger(__name__)


class PriorityLevel(Enum):
    """Priority levels for extensions."""
    CRITICAL = auto()    # System-critical extensions
    HIGH = auto()        # High priority extensions
    NORMAL = auto()      # Normal priority extensions
    LOW = auto()         # Low priority extensions
    FALLBACK = auto()    # Fallback/default extensions
    
    def __lt__(self, other):
        if self.__class__ is other.__class__:
            return self.value < other.value
        return NotImplemented
    
    def __int__(self):
        return self.value


@dataclass
class ExtensionPriority:
    """Represents priority information for an extension."""
    extension_name: str
    priority_level: PriorityLevel
    priority_score: int = 0  # Higher score = higher priority within level
    specificity_score: float = 0.0  # How specific this extension is (0.0 to 1.0)
    compatibility_score: float = 0.0  # Compatibility with current system (0.0 to 1.0)
    metadata: Dict[str, Any] = field(default_factory=dict)
    
    @property
    def total_score(self) -> float:
        """Calculate total priority score."""
        level_weight = {
            PriorityLevel.CRITICAL: 10000,
            PriorityLevel.HIGH: 1000,
            PriorityLevel.NORMAL: 100,
            PriorityLevel.LOW: 10,
            PriorityLevel.FALLBACK: 1
        }
        
        base_score = level_weight[self.priority_level] + self.priority_score
        bonus = (self.specificity_score + self.compatibility_score) * 50
        
        return base_score + bonus
    
    def __lt__(self, other):
        if not isinstance(other, ExtensionPriority):
            return NotImplemented
        return self.total_score < other.total_score


@dataclass
class ConflictResolution:
    """Represents a conflict resolution result."""
    selected_extension: str
    conflicting_extensions: List[str]
    resolution_method: str
    confidence: float  # 0.0 to 1.0
    explanation: str
    warnings: List[str] = field(default_factory=list)
    metadata: Dict[str, Any] = field(default_factory=dict)


class ConflictResolver(ABC):
    """Abstract base class for conflict resolvers."""
    
    @abstractmethod
    def resolve(self, 
               candidates: List[str],
               priorities: Dict[str, ExtensionPriority],
               context: Dict[str, Any]) -> ConflictResolution:
        """
        Resolve conflicts between candidate extensions.
        
        Args:
            candidates: List of candidate extension names
            priorities: Dictionary mapping extension names to priorities
            context: Additional context for resolution
            
        Returns:
            Conflict resolution result
        """
        pass


class HighestPriorityResolver(ConflictResolver):
    """Resolves conflicts by selecting the highest priority extension."""
    
    def resolve(self, 
               candidates: List[str],
               priorities: Dict[str, ExtensionPriority],
               context: Dict[str, Any]) -> ConflictResolution:
        """Resolve by selecting highest priority extension."""
        if not candidates:
            raise ValueError("No candidates provided")
        
        if len(candidates) == 1:
            return ConflictResolution(
                selected_extension=candidates[0],
                conflicting_extensions=[],
                resolution_method='no_conflict',
                confidence=1.0,
                explanation=f"Only one candidate: {candidates[0]}"
            )
        
        # Sort candidates by priority
        candidate_priorities = []
        for candidate in candidates:
            priority = priorities.get(candidate)
            if priority:
                candidate_priorities.append((candidate, priority))
            else:
                # Default priority for unregistered extensions
                default_priority = ExtensionPriority(
                    extension_name=candidate,
                    priority_level=PriorityLevel.NORMAL,
                    priority_score=0
                )
                candidate_priorities.append((candidate, default_priority))
        
        # Sort by total score (highest first)
        candidate_priorities.sort(key=lambda x: x[1].total_score, reverse=True)
        
        selected = candidate_priorities[0][0]
        selected_priority = candidate_priorities[0][1]
        
        # Check if there's a clear winner
        if len(candidate_priorities) > 1:
            second_best_score = candidate_priorities[1][1].total_score
            score_diff = selected_priority.total_score - second_best_score
            confidence = min(1.0, score_diff / max(1.0, selected_priority.total_score))
        else:
            confidence = 1.0
        
        explanation = f"Selected {selected} with priority {selected_priority.priority_level.name} (score: {selected_priority.total_score:.1f})"
        
        return ConflictResolution(
            selected_extension=selected,
            conflicting_extensions=[c for c in candidates if c != selected],
            resolution_method='highest_priority',
            confidence=confidence,
            explanation=explanation,
            metadata={
                'selected_score': selected_priority.total_score,
                'all_scores': {name: priority.total_score for name, priority in candidate_priorities}
            }
        )


class SpecificityResolver(ConflictResolver):
    """Resolves conflicts by selecting the most specific extension."""
    
    def resolve(self, 
               candidates: List[str],
               priorities: Dict[str, ExtensionPriority],
               context: Dict[str, Any]) -> ConflictResolution:
        """Resolve by selecting most specific extension."""
        if not candidates:
            raise ValueError("No candidates provided")
        
        if len(candidates) == 1:
            return ConflictResolution(
                selected_extension=candidates[0],
                conflicting_extensions=[],
                resolution_method='no_conflict',
                confidence=1.0,
                explanation=f"Only one candidate: {candidates[0]}"
            )
        
        # Find most specific extension
        best_candidate = None
        best_specificity = -1.0
        candidate_scores = {}
        
        for candidate in candidates:
            priority = priorities.get(candidate)
            specificity = priority.specificity_score if priority else 0.0
            candidate_scores[candidate] = specificity
            
            if specificity > best_specificity:
                best_specificity = specificity
                best_candidate = candidate
        
        if best_candidate is None:
            best_candidate = candidates[0]  # Fallback
        
        # Calculate confidence based on specificity gap
        sorted_scores = sorted(candidate_scores.values(), reverse=True)
        if len(sorted_scores) > 1:
            confidence = min(1.0, (sorted_scores[0] - sorted_scores[1]) * 2)
        else:
            confidence = 1.0
        
        explanation = f"Selected {best_candidate} based on highest specificity score ({best_specificity:.2f})"
        
        return ConflictResolution(
            selected_extension=best_candidate,
            conflicting_extensions=[c for c in candidates if c != best_candidate],
            resolution_method='highest_specificity',
            confidence=confidence,
            explanation=explanation,
            metadata={'specificity_scores': candidate_scores}
        )


class CompatibilityResolver(ConflictResolver):
    """Resolves conflicts by selecting the most compatible extension."""
    
    def resolve(self, 
               candidates: List[str],
               priorities: Dict[str, ExtensionPriority],
               context: Dict[str, Any]) -> ConflictResolution:
        """Resolve by selecting most compatible extension."""
        if not candidates:
            raise ValueError("No candidates provided")
        
        if len(candidates) == 1:
            return ConflictResolution(
                selected_extension=candidates[0],
                conflicting_extensions=[],
                resolution_method='no_conflict',
                confidence=1.0,
                explanation=f"Only one candidate: {candidates[0]}"
            )
        
        # Find most compatible extension
        best_candidate = None
        best_compatibility = -1.0
        candidate_scores = {}
        
        for candidate in candidates:
            priority = priorities.get(candidate)
            compatibility = priority.compatibility_score if priority else 0.0
            candidate_scores[candidate] = compatibility
            
            if compatibility > best_compatibility:
                best_compatibility = compatibility
                best_candidate = candidate
        
        if best_candidate is None:
            best_candidate = candidates[0]  # Fallback
        
        # Calculate confidence
        sorted_scores = sorted(candidate_scores.values(), reverse=True)
        if len(sorted_scores) > 1:
            confidence = min(1.0, (sorted_scores[0] - sorted_scores[1]) * 2)
        else:
            confidence = 1.0
        
        explanation = f"Selected {best_candidate} based on highest compatibility score ({best_compatibility:.2f})"
        
        return ConflictResolution(
            selected_extension=best_candidate,
            conflicting_extensions=[c for c in candidates if c != best_candidate],
            resolution_method='highest_compatibility',
            confidence=confidence,
            explanation=explanation,
            metadata={'compatibility_scores': candidate_scores}
        )


class CompositeResolver(ConflictResolver):
    """Composite resolver that uses multiple resolution strategies."""
    
    def __init__(self, resolvers: List[Tuple[ConflictResolver, float]]):
        """
        Initialize composite resolver.
        
        Args:
            resolvers: List of (resolver, weight) tuples
        """
        self.resolvers = resolvers
        self.total_weight = sum(weight for _, weight in resolvers)
    
    def resolve(self, 
               candidates: List[str],
               priorities: Dict[str, ExtensionPriority],
               context: Dict[str, Any]) -> ConflictResolution:
        """Resolve using weighted combination of strategies."""
        if not candidates:
            raise ValueError("No candidates provided")
        
        if len(candidates) == 1:
            return ConflictResolution(
                selected_extension=candidates[0],
                conflicting_extensions=[],
                resolution_method='no_conflict',
                confidence=1.0,
                explanation=f"Only one candidate: {candidates[0]}"
            )
        
        # Get results from all resolvers
        resolver_results = []
        for resolver, weight in self.resolvers:
            try:
                result = resolver.resolve(candidates, priorities, context)
                resolver_results.append((result, weight))
            except Exception as e:
                logger.warning(f"Resolver {resolver.__class__.__name__} failed: {e}")
        
        if not resolver_results:
            # Fallback to first candidate
            return ConflictResolution(
                selected_extension=candidates[0],
                conflicting_extensions=candidates[1:],
                resolution_method='fallback',
                confidence=0.1,
                explanation="All resolvers failed, using fallback"
            )
        
        # Calculate weighted scores for each candidate
        candidate_scores = {}
        for candidate in candidates:
            candidate_scores[candidate] = 0.0
        
        total_weight_used = 0.0
        for result, weight in resolver_results:
            # Give points to selected candidate
            if result.selected_extension in candidate_scores:
                candidate_scores[result.selected_extension] += weight * result.confidence
                total_weight_used += weight
        
        # Normalize scores
        if total_weight_used > 0:
            for candidate in candidate_scores:
                candidate_scores[candidate] /= total_weight_used
        
        # Select best candidate
        best_candidate = max(candidate_scores, key=candidate_scores.get)
        best_score = candidate_scores[best_candidate]
        
        # Calculate confidence
        sorted_scores = sorted(candidate_scores.values(), reverse=True)
        if len(sorted_scores) > 1:
            confidence = min(1.0, (sorted_scores[0] - sorted_scores[1]) * 2)
        else:
            confidence = 1.0
        
        explanation = f"Selected {best_candidate} using composite resolution (score: {best_score:.2f})"
        
        return ConflictResolution(
            selected_extension=best_candidate,
            conflicting_extensions=[c for c in candidates if c != best_candidate],
            resolution_method='composite',
            confidence=confidence,
            explanation=explanation,
            metadata={
                'candidate_scores': candidate_scores,
                'resolver_results': [(r.resolution_method, r.selected_extension, r.confidence) for r, _ in resolver_results]
            }
        )


class PrioritySystem:
    """
    Main priority system that manages extension priorities and conflict resolution.
    
    Provides comprehensive priority management with pluggable conflict resolution
    strategies and detailed reporting.
    """
    
    def __init__(self):
        """Initialize the priority system."""
        self.priorities: Dict[str, ExtensionPriority] = {}
        self.resolver = self._create_default_resolver()
        self.rules: List[Callable[[str, Dict[str, Any]], ExtensionPriority]] = []
        
        logger.debug("Initialized PrioritySystem")
    
    def register_extension_priority(self, 
                                  extension_name: str,
                                  priority_level: PriorityLevel,
                                  priority_score: int = 0,
                                  specificity_score: float = 0.0,
                                  compatibility_score: float = 0.0,
                                  **metadata) -> None:
        """
        Register priority information for an extension.
        
        Args:
            extension_name: Name of the extension
            priority_level: Priority level
            priority_score: Additional score within priority level
            specificity_score: How specific this extension is (0.0 to 1.0)
            compatibility_score: System compatibility score (0.0 to 1.0)
            **metadata: Additional metadata
        """
        priority = ExtensionPriority(
            extension_name=extension_name,
            priority_level=priority_level,
            priority_score=priority_score,
            specificity_score=specificity_score,
            compatibility_score=compatibility_score,
            metadata=metadata
        )
        
        self.priorities[extension_name] = priority
        logger.debug(f"Registered priority for extension {extension_name}: {priority_level.name}")
    
    def update_compatibility_scores(self, scores: Dict[str, float]) -> None:
        """Update compatibility scores for registered extensions."""
        for extension_name, score in scores.items():
            if extension_name in self.priorities:
                self.priorities[extension_name].compatibility_score = max(0.0, min(1.0, score))
    
    def resolve_conflicts(self, 
                         candidates: List[str],
                         context: Optional[Dict[str, Any]] = None) -> ConflictResolution:
        """
        Resolve conflicts between candidate extensions.
        
        Args:
            candidates: List of candidate extension names
            context: Additional context for resolution
            
        Returns:
            Conflict resolution result
        """
        if context is None:
            context = {}
        
        # Apply dynamic priority rules
        for candidate in candidates:
            if candidate not in self.priorities:
                # Try to determine priority using rules
                for rule in self.rules:
                    try:
                        priority = rule(candidate, context)
                        if priority:
                            self.priorities[candidate] = priority
                            logger.debug(f"Applied rule-based priority for {candidate}")
                            break
                    except Exception as e:
                        logger.debug(f"Priority rule failed for {candidate}: {e}")
        
        # Use resolver to handle conflicts
        return self.resolver.resolve(candidates, self.priorities, context)
    
    def get_extension_priority(self, extension_name: str) -> Optional[ExtensionPriority]:
        """Get priority information for an extension."""
        return self.priorities.get(extension_name)
    
    def list_priorities(self) -> List[Tuple[str, ExtensionPriority]]:
        """List all registered priorities sorted by total score."""
        items = list(self.priorities.items())
        items.sort(key=lambda x: x[1].total_score, reverse=True)
        return items
    
    def add_priority_rule(self, rule: Callable[[str, Dict[str, Any]], Optional[ExtensionPriority]]) -> None:
        """
        Add a dynamic priority rule.
        
        Args:
            rule: Function that takes (extension_name, context) and returns ExtensionPriority or None
        """
        self.rules.append(rule)
        logger.debug("Added dynamic priority rule")
    
    def set_resolver(self, resolver: ConflictResolver) -> None:
        """Set the conflict resolver."""
        self.resolver = resolver
        logger.debug(f"Set conflict resolver to {resolver.__class__.__name__}")
    
    def clear_priorities(self) -> None:
        """Clear all registered priorities."""
        self.priorities.clear()
        logger.debug("Cleared all extension priorities")
    
    def _create_default_resolver(self) -> ConflictResolver:
        """Create default composite resolver."""
        return CompositeResolver([
            (HighestPriorityResolver(), 3.0),      # Priority is most important
            (SpecificityResolver(), 2.0),          # Specificity is secondary
            (CompatibilityResolver(), 1.0),        # Compatibility is tertiary
        ])
    
    def get_system_info(self) -> Dict[str, Any]:
        """Get information about the priority system."""
        return {
            'registered_extensions': len(self.priorities),
            'priority_rules': len(self.rules),
            'resolver_type': self.resolver.__class__.__name__,
            'priority_distribution': {
                level.name: sum(1 for p in self.priorities.values() if p.priority_level == level)
                for level in PriorityLevel
            }
        }


def create_framework_priority_rules() -> List[Callable[[str, Dict[str, Any]], Optional[ExtensionPriority]]]:
    """Create standard priority rules for common frameworks."""
    
    def original_gcr_rule(extension_name: str, context: Dict[str, Any]) -> Optional[ExtensionPriority]:
        """Give high priority to original GCR extensions."""
        if 'original_gcr' in extension_name.lower() or 'gcr' in extension_name.lower():
            return ExtensionPriority(
                extension_name=extension_name,
                priority_level=PriorityLevel.HIGH,
                priority_score=100,
                specificity_score=0.9,
                metadata={'rule': 'original_gcr_rule'}
            )
        return None
    
    def framework_specificity_rule(extension_name: str, context: Dict[str, Any]) -> Optional[ExtensionPriority]:
        """Assign priority based on framework specificity."""
        name_lower = extension_name.lower()
        
        if 'hybrid' in name_lower:
            # Hybrid frameworks are more specific
            return ExtensionPriority(
                extension_name=extension_name,
                priority_level=PriorityLevel.NORMAL,
                priority_score=80,
                specificity_score=0.8,
                metadata={'rule': 'framework_specificity_rule', 'type': 'hybrid'}
            )
        elif any(framework in name_lower for framework in ['petsc', 'kokkos']):
            # Single framework extensions
            return ExtensionPriority(
                extension_name=extension_name,
                priority_level=PriorityLevel.NORMAL,
                priority_score=60,
                specificity_score=0.6,
                metadata={'rule': 'framework_specificity_rule', 'type': 'single_framework'}
            )
        
        return None
    
    def compatibility_rule(extension_name: str, context: Dict[str, Any]) -> Optional[ExtensionPriority]:
        """Assign priority based on system compatibility."""
        # This would need actual system detection logic
        config_path = context.get('config_path', '')
        
        if 'cuda' in config_path.lower():
            if 'cuda' in extension_name.lower():
                return ExtensionPriority(
                    extension_name=extension_name,
                    priority_level=PriorityLevel.NORMAL,
                    compatibility_score=0.9,
                    metadata={'rule': 'compatibility_rule', 'detected': 'cuda'}
                )
        
        if 'hip' in config_path.lower():
            if 'hip' in extension_name.lower():
                return ExtensionPriority(
                    extension_name=extension_name,
                    priority_level=PriorityLevel.NORMAL,
                    compatibility_score=0.9,
                    metadata={'rule': 'compatibility_rule', 'detected': 'hip'}
                )
        
        return None
    
    return [original_gcr_rule, framework_specificity_rule, compatibility_rule]


# Global priority system instance
priority_system = PrioritySystem()

# Add standard priority rules
for rule in create_framework_priority_rules():
    priority_system.add_priority_rule(rule)